/*+***********************************************************************************
 Filename: 9k_mcu02_mycore_v01\src\mem\zh_dprom_v02.v
 Description: dual-port rom with sync read clock implement by verilog.
   With initial code instructions.
   pos-edge of clk latch the address.
   one port read-only, one port read-write (for jtag).
              
 Modification:
   2025.08.17 Creation   H.Zheng
   2025.08.18 modified from zh_dprom_v01

Copyright (C) 2025  Zheng Hui (hzheng@gzhu.edu.cn)

License: MulanPSL-2.0

***********************************************************************************-*/

module zh_dprom_v02 #(parameter ROM_SIZE_IN_KB=1, localparam ADDR_BUS_WIDTH=clogb2(ROM_SIZE_IN_KB*256-1))(

  //port a read only
	input wire clka,
  input wire cea,
  input wire [ADDR_BUS_WIDTH-1:0] addra,
  output wire [31:0] douta,
  //port b read write
	input wire clkb, //clk to update address
  input wire ceb,
  input wire [ADDR_BUS_WIDTH-1:0] addrb,
  output wire [31:0] doutb,
  input wire wclkb, //write clock
  input wire wenb, //write enable
  input wire [31:0] dinb
);

  //storage
  reg [7:0] BRAM[0:ROM_SIZE_IN_KB*1024-1];  //one byte per memory unit because code.txt is byte seperated

  initial begin
    $readmemh("code.txt", BRAM);
  end

  reg [ADDR_BUS_WIDTH-1:0] addr_a_r;
  always @(posedge clka) begin
    if (cea)
      addr_a_r <= addra;
  end
  
  wire [ADDR_BUS_WIDTH+1:0] addr_a_b0 = {addr_a_r,2'b00};
  wire [ADDR_BUS_WIDTH+1:0] addr_a_b1 = {addr_a_r,2'b01};
  wire [ADDR_BUS_WIDTH+1:0] addr_a_b2 = {addr_a_r,2'b10};
  wire [ADDR_BUS_WIDTH+1:0] addr_a_b3 = {addr_a_r,2'b11};

  assign douta = { BRAM[addr_a_b3], BRAM[addr_a_b2], BRAM[addr_a_b1], BRAM[addr_a_b0]};

  reg [ADDR_BUS_WIDTH-1:0] addr_b_r;
  always @(posedge clkb) begin
    if (ceb)
      addr_b_r <= addrb;
  end
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b0 = {addr_b_r,2'b00};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b1 = {addr_b_r,2'b01};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b2 = {addr_b_r,2'b10};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b3 = {addr_b_r,2'b11};

  assign doutb = { BRAM[addr_b_b3], BRAM[addr_b_b2], BRAM[addr_b_b1], BRAM[addr_b_b0]};

  //write
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b0_w = {addrb,2'b00};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b1_w = {addrb,2'b01};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b2_w = {addrb,2'b10};
  wire [ADDR_BUS_WIDTH+1:0] addr_b_b3_w = {addrb,2'b11};
  always @(posedge wclkb) begin
    if(ceb& wenb) begin
      BRAM[addr_b_b0_w] <= dinb[7:0];
      BRAM[addr_b_b1_w] <= dinb[15:8];
      BRAM[addr_b_b2_w] <= dinb[23:16];
      BRAM[addr_b_b3_w] <= dinb[31:24];
    end
  end


  //
  function integer clogb2;
    input integer depth;
      for (clogb2=0; depth>0; clogb2=clogb2+1)
        depth = depth >> 1;
  endfunction

endmodule